package com.gl.battleship.services.managers;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

import com.gl.battleship.domain.Game;
import com.gl.battleship.domain.GameState;
import com.gl.battleship.domain.Notification;
import com.gl.battleship.domain.NotificationType;
import com.gl.battleship.domain.Player;
import com.gl.battleship.domain.Position;
import com.gl.battleship.domain.Ship;
import com.gl.battleship.domain.ShipState;
import com.gl.battleship.domain.StatePosition;
import com.gl.battleship.domain.User;
import com.gl.battleship.domain.UserState;
import com.gl.battleship.domain.exceptions.PlayerException;
import com.gl.battleship.dto.PositionDto;
import com.gl.battleship.services.Constants;
import com.gl.battleship.services.exceptions.GameException;
import com.gl.battleship.services.exceptions.MessagesExceptions;

/**
 * 
 * @author simon.gomez
 * @author mariana.capponetto
 * @author romina.tornello
 * @author armando.rosario
 * @version 1.0
 */

@Service
@Scope("singleton")
public class GameManager implements IGameManager {

	private Map<String, Game> games = new HashMap<String, Game>();
	
	
	/**
	 * Create the new Game between two participants
	 * 
	 * @param playerOne is the player who declared the war
	 * @param playerTwo is the player to whom it declared the war
	 * @return String that is the identifier of the war
	 * @throws PlayerException 
	 */
	private Game createGame(Player playerOne, Player playerTwo) throws PlayerException  {
		boolean wartokenValid = false;

		String wartoken;
		
		do {
			wartoken = "token" + Math.round((Math.random() * 100));

			if (this.games.get(wartoken) == null) {
				wartokenValid = true;
			}

		} while (!wartokenValid);

		Game game = new Game(playerOne, playerTwo);
		game.setWarkToken(wartoken);
		game.setTurnoDisparar(playerOne);
		game.setStateGame(GameState.WAITING_TO_ACCEPT_WAR);
		this.games.put(wartoken, game);
		
		return game;
	}

	
	/**
	 * Get the Game identified with the String
	 * 
	 * @param wartoken is an String that identifier the war
	 * @return the game
	 */

	private Game getGame(String wartoken) throws GameException {
		
		if(validateWarToken(wartoken)){
		Game game = this.games.get(wartoken);
			return game;
		}
		else{
			throw new GameException(MessagesExceptions.INVALID_WARTOKEN);
		}
	}

	/**
	 * Validate the war token
	 * 
	 * @param warToken to be validate
	 * @return true or false
	 */
	public boolean validateWarToken(String warToken){
		    return games.containsKey(warToken);  
		 }
	
	
	
	
	
	/**
	 * Finish the game
	 * 
	 * @param wartoken is an String that identifier the war
	 * @return the game
	 */
	private Game endGame(String wartoken) throws GameException {	
		if (validateWarToken(wartoken)) {
			Game game = this.games.get(wartoken);
			game.setStateGame(GameState.END);
			return game;
		}else
			{
			throw new GameException(MessagesExceptions.INVALID_WARTOKEN);
			}
	}
	
	
	/**
	 * Validate a Fleet 
	 * 
	 * @param user is the User that validate the fleet
	 * @param warToken is an String that identified the war
	 * @param fleet is an String that arm the fleet
	 * @throws GameException 
	 * @throws PlayerException 
	 * @return result obtained of the fleet validate: 'VALID' or an Exception
	 */
	public String validateFleet(User user, String warToken, String fleet)throws GameException, PlayerException {
		
	String answerToValidateFleet=Constants.INVALID;
	
	if(validateWarToken(warToken)){	

		/*
		 * Validations for warToken
		 */

		// The game for the warToken choose
		// if null throws GameException
		Game game = this.getGame(warToken);

		// Validate if the game is waiting for set the fleets
		if (game.getStateGame() != GameState.WAR_ACCEPTED) {
			throw new GameException(MessagesExceptions.INVALID_GAME_STATE);
		}
		
		// Validate if the player is on the game
		// if null throws PlayerException
		Player player = game.getPlayerByUser(user);
		

		/*
		 * Validations for the fleet
		 */

		// Validate the lenght of String fleet
		// The lenght of board for each player is 100 boxes
		if (fleet.length() != (Constants.SIZE_OF_BOARD * Constants.SIZE_OF_BOARD)) {
			throw new GameException(MessagesExceptions.INVALID_SIZE_BOARD);
		}

		// Validate the characters invalids
		if (!GameManagerHelper.validateCharsFleet(fleet)) {
			throw new GameException(MessagesExceptions.INVALID_CHAR_FLEET);
		}

		// Validate the length of each ship
		if (!GameManagerHelper.validateLengthOfShips(fleet)) {
			throw new GameException(MessagesExceptions.INVALID_LENGTH_SHIP);
		}

		// Matrix for board
		char board[][] = new char[Constants.SIZE_OF_BOARD][Constants.SIZE_OF_BOARD];

		// Index of string fleet
		int stringFleetPosition = 0;

		// Fill the matrix
		for (int verticalSide = 0; verticalSide < Constants.SIZE_OF_BOARD; verticalSide++) {
			for (int horizontalSide = 0; horizontalSide < Constants.SIZE_OF_BOARD; horizontalSide++) {
				board[verticalSide][horizontalSide] = fleet
						.charAt(stringFleetPosition);
				stringFleetPosition++;
			}
		}

		// Solo muestra el tablero no va en la clase
		for (int horizontalSide = 0; horizontalSide < 10; horizontalSide++) {
			for (int verticalSide = 0; verticalSide < 10; verticalSide++) {
//				System.out.print(board[horizontalSide][verticalSide]);
			}
		}

		// Initialize ships
		Ship shipTwoPositions = new Ship(ShipState.NO_SUNK);
		Ship shipThreePositions = new Ship(ShipState.NO_SUNK);
		Ship shipFourPositions = new Ship(ShipState.NO_SUNK);
		Ship shipFivePositions = new Ship(ShipState.NO_SUNK);

		// Initialize the positions of ships with the values ​​of the matrix
		for (int y = 0; y < Constants.SIZE_OF_BOARD; y++) {
			
			for (int x = 0; x < Constants.SIZE_OF_BOARD; x++) {
				
				if (board[y][x] != Constants.WATER) {
					
					Position position = new Position(x,
							y, StatePosition.NO_HIT);

					switch (board[y][x]) {
					case Constants.SHIP_TWO_POSITIONS:
						shipTwoPositions.addPosition(position);
						break;

					case Constants.SHIP_THREE_POSITIONS:
						shipThreePositions.addPosition(position);
						break;

					case Constants.SHIP_FOUR_POSITIONS:
						shipFourPositions.addPosition(position);
						break;

					case Constants.SHIP_FIVE_POSITIONS:
						shipFivePositions.addPosition(position);
						break;
					}// switch
				}// if
			}// for verticalSide
		}// for horizontalSide

		// Create the fleet
		List<Ship> shipList = new ArrayList<Ship>();
		shipList.add(shipTwoPositions);
		shipList.add(shipThreePositions);
		shipList.add(shipFourPositions);
		shipList.add(shipFivePositions);

		// Validate ship Positions
		if (!GameManagerHelper.validateShipPositions(shipList)) {
			throw new GameException(MessagesExceptions.INVALID_SHIP_POS);
		}

		// Fleet ready for war!
		user.setLastActivity( new Date() );
		
		player.setFleet(shipList);
		answerToValidateFleet=Constants.VALID_FLEET;
		NotificationManager.removeNotification(game.getWarkToken(),NotificationType.ACCEPTED_WAR);				
		
		if(game.getPlayerOne().getFleet().size()>0 && game.getPlayerTwo().getFleet().size()>0){
			Notification notification = new Notification();
			notification.setTypeNotification(NotificationType.TURN_TO_SHOOT);
			notification.setGame(game);
			notification.setUser(game.getTurnoDisparar().getUser());
			NotificationManager.addNotificationTurnShoot(notification);
		}
		
		return answerToValidateFleet;
	}else
		throw new GameException(MessagesExceptions.INVALID_WARTOKEN);
	}

	public String shoot(String warToken, int x, String y, User user) throws GameException, PlayerException{
		
		if(validateWarToken(warToken)){
		int numbery = -1;
		if(y == null && y.trim().isEmpty()){
			throw new GameException(MessagesExceptions.INVALID_VALUE);
		}
		
		if(!y.equalsIgnoreCase("a") && !y.equalsIgnoreCase("b") && !y.equalsIgnoreCase("c") && !y.equalsIgnoreCase("d") && !y.equalsIgnoreCase("e") &&
		   !y.equalsIgnoreCase("f") && !y.equalsIgnoreCase("g") && !y.equalsIgnoreCase("h") && !y.equalsIgnoreCase("i") && !y.equalsIgnoreCase("j")
		  ){
			throw new GameException(MessagesExceptions.INVALID_TYPE_VALUE);
		}else{
			if(y.equalsIgnoreCase("a")){
				numbery = 0;
			}else if(y.equalsIgnoreCase("b")){
				numbery = 1;
			}else if(y.equalsIgnoreCase("c")){
				numbery = 2;
			}else if(y.equalsIgnoreCase("d")){
				numbery = 3;
			}else if(y.equalsIgnoreCase("e")){
				numbery = 4;
			}else if(y.equalsIgnoreCase("f")){
				numbery = 5;
			}else if(y.equalsIgnoreCase("g")){
				numbery = 6;
			}else if(y.equalsIgnoreCase("h")){
				numbery = 7;
			}else if(y.equalsIgnoreCase("i")){
				numbery = 8;
			}else if(y.equalsIgnoreCase("j")){
				numbery = 9;
			}

		}
		
		if(x<0 && x>9){
			throw new GameException(MessagesExceptions.INVALID_SIZE_VALUE);
		}
	
		Game game = this.getGame(warToken);
		Player player = game.getPlayerByUser(user);
		if(!player.getUser().getNick().equalsIgnoreCase(game.getTurnoDisparar().getUser().getNick() )){
			throw new GameException(MessagesExceptions.PLAYER_NOT_TURN_SHOOT);
		}
		
		String result ="";
		if(player.getUser().getNick().equalsIgnoreCase(game.getPlayerOne().getUser().getNick())){
			result = game.getPlayerTwo().shoot(x, numbery);
			
		}else{
			result = game.getPlayerOne().shoot(x, numbery);
			
		}

		if(result.equalsIgnoreCase(Constants.WATER_STRING)){
			if(player.getUser().getNick().equalsIgnoreCase(game.getPlayerOne().getUser().getNick())){
				game.setTurnoDisparar(game.getPlayerTwo());
			}else{
				game.setTurnoDisparar(game.getPlayerOne());
			}

		}else{
			if(Constants.HIT_SUNK_WIN.equals(result)){
				game.getPlayerOne().getUser().setStateUser(UserState.AVAILABLE);
				game.getPlayerTwo().getUser().setStateUser(UserState.AVAILABLE);
			}
			game.setTurnoDisparar(player);
		}
		
		Player playerPosition = null;
		if(player.getUser().getNick().equalsIgnoreCase(game.getPlayerOne().getUser().getNick())){
			playerPosition = game.getPlayerTwo();
		}else{
			playerPosition = game.getPlayerOne();
		}
		
		PositionDto positionDto = new PositionDto();
		positionDto.setX(x);
		positionDto.setY(y);
		
		NotificationManager.addNotificationShoots(game, NotificationType.POSITIONS_X_Y, positionDto, playerPosition);

		Notification notification = new Notification();
		notification.setTypeNotification(NotificationType.TURN_TO_SHOOT);
		notification.setGame(game);
		notification.setUser(game.getTurnoDisparar().getUser());
		NotificationManager.addNotificationTurnShoot(notification);
				
		user.setLastActivity( new Date() );
		NotificationManager.removeNotification(game.getWarkToken(),NotificationType.ACCEPTED_WAR);
		
		return result;
		}else
			throw new GameException(MessagesExceptions.INVALID_WARTOKEN);
	}

	
	/**
	 * User declares war on another user
	 * 
	 * @param user the User who declare the war
	 * @param opponent the User to whom it declared the war 
	 * @return the Game declared
	 * @throws PlayerException
	 * @throws GameException
	 */
	public Game declareWar(User user, User opponent)  throws PlayerException, GameException {
				
		// Validate available users
		if (user.getStateUser() == UserState.NOT_AVAILABLE ||
				opponent.getStateUser() == UserState.NOT_AVAILABLE) {
			throw new GameException(MessagesExceptions.PLAYER_BUSY);
		}
		
		Player playerOne = new Player(); 
		playerOne.setUser(user);
		Player playerTwo = new Player(); 
		playerTwo.setUser(opponent);
		
		user.setLastActivity( new Date() );		
		
		Notification notify = new Notification();
		Game game = createGame(playerOne, playerTwo);
		notify.setUser(opponent);
		notify.setGame(game);
		notify.setTypeNotification(NotificationType.DECLARED_WAR);
		NotificationManager.notifications.add(notify);
		
		return game;
	}
	
	
	
	/**
	 * Accept the war declared
	 * 
	 * @param user2  is the User who accepted the war
	 * @param warToken is a String that identified the war declared
	 * @throws GameException 
	 * @return  a String 'VALID' that confirms the war accepted or 'INVALID'
	 */
	public String acceptWar(User user2,String warToken) throws GameException{
	
		if(validateWarToken(warToken)){
		String answerToAccept=Constants.INVALID;
		Game game = this.getGame(warToken);

		// Validate if the game is waiting to accept the war
		if (game.getStateGame() != GameState.WAITING_TO_ACCEPT_WAR) {
			throw new GameException(MessagesExceptions.INVALID_GAME_STATE);
		}
		game.setStateGame(GameState.WAR_ACCEPTED);
		game.getPlayerOne().getUser().setStateUser(UserState.NOT_AVAILABLE);
		user2.setStateUser(UserState.NOT_AVAILABLE);
		user2.setLastActivity( new Date() );
		answerToAccept=Constants.ACCEPTED_WAR;
		
		Notification NewNotify = new Notification();
		NewNotify.setUser(game.getPlayerOne().getUser());
		NewNotify.setGame(game);
		NewNotify.setTypeNotification(NotificationType.ACCEPTED_WAR);
		NotificationManager.notifications.add(NewNotify);
		
		NotificationManager.removeNotifications(game, NotificationType.DECLARED_WAR);
		
		return answerToAccept;
		}else{
			throw new GameException(MessagesExceptions.INVALID_WARTOKEN);
			}
	}
	
	
	/**
	 * Refuse the war declared
	 * 
	 * @param user2 is the User who refused the war
	 * @param warToken is a String that identified the war declared
	 * @throws GameException 
	 * @return a String 'VALID' that confirms the war refused
	 */	
	public String refuseWar(User user2,String warToken) throws GameException{
		
		if(validateWarToken(warToken)){
		String answerToRefuse=Constants.INVALID;
		Game game = this.getGame(warToken);
		if (game.getStateGame() != GameState.WAITING_TO_ACCEPT_WAR) {
			throw new GameException(MessagesExceptions.INVALID_GAME_STATE);
		}
		game.setStateGame(GameState.END);
		answerToRefuse=Constants.REFUSED_WAR;
		user2.setLastActivity( new Date() );
		
		Notification newNotify = new Notification();
		newNotify.setUser(game.getPlayerOne().getUser());
		newNotify.setGame(game);
		newNotify.setTypeNotification(NotificationType.REFUSED_WAR);
		NotificationManager.notifications.add(newNotify);
		
		NotificationManager.removeNotification(game.getWarkToken(),NotificationType.DECLARED_WAR);
		
		return answerToRefuse;
	}else{
		throw new GameException(MessagesExceptions.INVALID_WARTOKEN);
		}
}
	
	
	/**
	 * Retired the war declared
	 * 
	 * @param user is the User who canceled the war
	 * @param warToken is an String that identified the war declared
	 * @return a String 'RETIRED' that confirms the war canceled
	 * @throws GameException 
	 */	
//	public String retiredWar(User user) throws GameException  {
//		
//		Notification notify = new Notification();
//		
//		Iterator it = this.games.entrySet().iterator();
//		while (it.hasNext()) {
//			Map.Entry gam = (Map.Entry)it.next();
//			
//			if( ((Game)gam.getValue()).getPlayerOne().getUser().getNick().equals(user.getNick())){
//				((Game)gam.getValue()).getPlayerTwo().getUser().setStateUser(UserState.AVAILABLE);
//				Game game = endGame( (String)gam.getKey() );
//				notify.setGame(game);
//				notify.setUser(game.getPlayerTwo().getUser());
//				NotificationManager.removeNotification(game.getWarkToken(),NotificationType.ACCEPTED_WAR);
//			}
//			if( ((Game)gam.getValue()).getPlayerTwo().getUser().getNick().equals(user.getNick())){
//				((Game)gam.getValue()).getPlayerOne().getUser().setStateUser(UserState.AVAILABLE);
//				Game game = endGame( (String)gam.getKey() );
//				notify.setGame(game);
//				notify.setUser(game.getPlayerOne().getUser());
//				NotificationManager.removeNotification(game.getWarkToken(),NotificationType.ACCEPTED_WAR);
//			}
//					
//		}
//		user.setLastActivity( new Date() );	
//		notify.setTypeNotification(NotificationType.RETIRED_WAR);
//		NotificationManager.notifications.add(notify);
//	
//		return Constants.RETIRED;
//	}
	
	public String retiredWar(String nick) throws GameException  {
		
		Notification notify = new Notification();
		
		Iterator it = this.games.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry gam = (Map.Entry)it.next();
			
			if( ((Game)gam.getValue()).getPlayerOne().getUser().getNick().equals(nick)){
				((Game)gam.getValue()).getPlayerTwo().getUser().setStateUser(UserState.AVAILABLE);
				Game game = endGame( (String)gam.getKey() );
				User user=((Game) gam.getValue()).getPlayerOne().getUser();
				user.setLastActivity( new Date() );	
				notify.setGame(game);
				notify.setUser(game.getPlayerTwo().getUser());
				NotificationManager.removeNotification(game.getWarkToken(),NotificationType.ACCEPTED_WAR);
			}
			if( ((Game)gam.getValue()).getPlayerTwo().getUser().getNick().equals(nick)){
				((Game)gam.getValue()).getPlayerOne().getUser().setStateUser(UserState.AVAILABLE);
				Game game = endGame( (String)gam.getKey() );
				User user=((Game) gam.getValue()).getPlayerTwo().getUser();
				user.setLastActivity( new Date() );	
				notify.setGame(game);
				notify.setUser(game.getPlayerOne().getUser());
				NotificationManager.removeNotification(game.getWarkToken(),NotificationType.ACCEPTED_WAR);
			}
					
		}
		notify.setTypeNotification(NotificationType.RETIRED_WAR);
		NotificationManager.notifications.add(notify);
	
		return Constants.RETIRED;
	}	


	
}
